package br.jm.i18n;

import static org.objectweb.asm.Opcodes.ACC_FINAL;
import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
import static org.objectweb.asm.Opcodes.ACC_VARARGS;
import static org.objectweb.asm.Opcodes.ALOAD;
import static org.objectweb.asm.Opcodes.ARETURN;
import static org.objectweb.asm.Opcodes.ASM4;
import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
import static org.objectweb.asm.Opcodes.INVOKESTATIC;
import static org.objectweb.asm.Opcodes.RETURN;

import java.lang.invoke.MethodType;
import java.text.MessageFormat;
import java.util.Locale;

import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Type;

public class I18NGenerator extends ClassVisitor {

	class KeyCollector extends MethodVisitor {

		final String name;
		final boolean hasArgs;
		String key;

		public KeyCollector(final MethodVisitor mv, final String name, final boolean hasArgs) {
			super(ASM4, mv);
			this.name = name;
			this.hasArgs = hasArgs;
		}

		@Override
		public AnnotationVisitor visitAnnotation(final String desc, final boolean visible) {
			if (I18N_DESC.equals(desc)) {
				return new AnnotationVisitor(ASM4) {

					@Override
					public void visit(final String name, final Object value) {
						key = (String) value;
					}

				};
			}

			return null;
		}

		@Override
		public void visitCode() {
			if ((key == null) || key.isEmpty()) {
				key = keyByConvention();
			}

			final String value = ms.getMessage(key, loc);

			visitLdcInsn(value);

			if (hasArgs) {
				visitVarInsn(ALOAD, 1);
				visitMethodInsn(INVOKESTATIC, Type.getInternalName(MessageFormat.class), "format", MF_DESC);
			}

			visitInsn(ARETURN);
			visitMaxs(0, 0);
		}

		private String keyByConvention() {
			return originalName.replace('/', '.') + "." + name;
		}

		@Override
		public void visitEnd() {
			visitCode();

			super.visitEnd();
		}

	}

	static final String I18N_DESC = Type.getDescriptor(I18N.class);

	static final String NO_ARGS_DESC = MethodType.methodType(String.class).toMethodDescriptorString();

	static final String WITH_ARGS_DESC = MethodType.methodType(String.class, Object[].class).toMethodDescriptorString();

	static final String MF_DESC = MethodType.methodType(String.class, String.class, Object[].class).toMethodDescriptorString();

	final MessageProvider ms;
	final Locale loc;
	String targetName;
	String originalName;

	public I18NGenerator(final ClassVisitor cv, final MessageProvider ms, final Locale loc) {
		super(ASM4, cv);
		this.ms = ms;
		this.loc = loc;
	}

	@Override
	public void visit(final int version, final int access, final String name, final String signature, final String superName, final String[] interfaces) {
		originalName = name;
		targetName = name + "@" + loc;
		super.visit(version, ACC_FINAL + ACC_PUBLIC, targetName, signature, superName, new String[]{ name });
	}

	@Override
	public MethodVisitor visitMethod(final int access, final String name, final String desc, final String signature, final String[] exceptions) {
		final MethodVisitor mv = super.visitMethod(ACC_FINAL + ACC_PUBLIC + (access & ACC_VARARGS), name, desc, signature, exceptions);

		final boolean hasParams = WITH_ARGS_DESC.equals(desc);

		if (!hasParams && !NO_ARGS_DESC.equals(desc)) {
			throw new IllegalArgumentException(MessageFormat.format("Valid descriptors: {0}/{1}", NO_ARGS_DESC, WITH_ARGS_DESC));
		}

		return new KeyCollector(mv, name, hasParams);
	}

	@Override
	public void visitEnd() {

		emitCtor();

		super.visitEnd();
	}

	private void emitCtor() {
		final MethodVisitor mv = super.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
		mv.visitCode();
		mv.visitVarInsn(ALOAD, 0);
		mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V");
		mv.visitInsn(RETURN);
		mv.visitMaxs(0, 0);
		mv.visitEnd();
	}
}